<?php

namespace common\widgets;

use Yii;
use Closure;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use common\helpers\MenuHelper;

/**
 * LeftMenu displays a multi-level menu using nested HTML lists.
 *
 * ```php
 * echo LeftMenu::widget([
 *     'options' => [
 *          'class' => 'page-sidebar-menu page-sidebar-menu-light',
 *          'data-keep-expanded' => false,
 *          'data-auto-scroll' => true,
 *          'data-slide-speed' => 100,
 *     ]
 * ]);
 * ```
 *
 * @author emhome<emhome@163.com>
 * @since 1.0
 */
class LeftMenu extends Widget {

    /**
     * @var array list of menu items. 
     */
    public $items = [];

    /**
     * @var array list of HTML attributes shared by all menu [[items]].
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $itemOptions = [];

    /**
     * @var array list of HTML attributes shared by all menu [[items]].
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $submenuOptions = ['class' => 'sub-menu'];

    /**
     * @var string the template used to render the body of a menu which is a link.
     */
    public $linkOptions = ['class' => 'nav-link nav-toggle'];

    /**
     * @var string the template used to render the body of a menu which is a link.
     */
    public $linkTemplate = '{icon}{label}{badge}';

    /**
     * @var string the template used to render the body of a menu which is NOT a link.
     */
    public $labelOptions = [];

    /**
     * @var string the template used to render the body of a menu which is NOT a link.
     */
    public $badgeCssClass = 'badge badge-danger';

    /**
     * @var string the template used to render the body of a menu which is NOT a link.
     */
    public $toggleCssClass = 'open';

    /**
     * @var bool whether the labels for menu items should be HTML-encoded.
     */
    public $encodeLabels = true;

    /**
     * @var string the CSS class to be appended to the active menu item.
     */
    public $activeCssClass = 'active';

    /**
     * @var bool whether to automatically activate items according to whether their route setting
     * @see isItemActive()
     */
    public $activateItems = true;

    /**
     * @var bool whether to activate parent menu items when one of the corresponding child menu items is active.
     */
    public $activateParents = true;

    /**
     * @var bool whether to hide empty menu items.
     */
    public $hideEmptyItems = true;

    /**
     * @var array the HTML attributes for the menu's container tag.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $options = [];

    /**
     * @var string the route used to determine if a menu item is active or not.
     */
    public $route;

    /**
     * @var array the parameters used to determine if a menu item is active or not.
     */
    public $params;

    /**
     * Renders the menu.
     */
    public function run() {
        if ($this->route === null && Yii::$app->controller !== null) {
            $this->route = Yii::$app->controller->getRoute();
        }
        if ($this->params === null) {
            $this->params = Yii::$app->request->getQueryParams();
        }

        $itemMenus = ArrayHelper::merge($this->authMenus, $this->items);
        $items = $this->normalizeItems($itemMenus, $hasActiveChild);

        $content = $this->renderItems($items);
        $tag = ArrayHelper::remove($this->options, 'tag', 'ul');
        return $this->groupMenus . Html::tag($tag, $content, $this->options);
    }

    /**
     * 获取左侧头菜单
     */
    public function getGroupMenus() {
        $view = $this->getView();

        $moduleClass = $view->context->module;

        $menuHeaderName = ArrayHelper::getValue($view->blocks, 'content-header', Yii::t('module/name', $moduleClass->id));

        if (Yii::$app->id !== $moduleClass->id) {
            $view->params['breadcrumbs'][] = [
                'label' => $menuHeaderName,
                'url' => '/' . $moduleClass->id
            ];
        }

        return Html::tag('div', $menuHeaderName, ['class' => 'module-menu-title']);
    }

    /**
     * 获取授权菜单列表
     */
    public function getAuthMenus() {
        $menus = [];
        $view = $this->getView();

        $moduleClass = $view->context->module;

        if ($moduleClass instanceof \mdm\admin\Module) {
            $menus = $moduleClass->menus;
            foreach ($menus as $key => $menu) {
                $menus[$key]['active'] = strpos($view->context->route, trim($menu['url'][0], '/')) === 0;
            }
        } elseif ($moduleClass instanceof \common\components\BaseModule) {
            $menus = $moduleClass->menus;
        } else {
            $menus = MenuHelper::getAssignedMenu(Yii::$app->user->id);
        }

        return $menus;
    }

    /**
     * Recursively renders the menu items (without the container tag).
     * @param array $items the menu items to be rendered recursively
     * @return string the rendering result
     */
    protected function renderItems($items) {
        if (empty($items)) {
            return '';
        }

        $lines = [];
        $i = 0;
        foreach ($items as $item) {
            $i++;
            $options = ArrayHelper::merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
            $tag = ArrayHelper::remove($options, 'tag', 'li');

            if ($item['active']) {
                Html::addCssClass($options, $this->activeCssClass);
                if (!empty($item['items'])) {
                    Html::addCssClass($options, $this->toggleCssClass);
                }
            }

            $menu = $this->renderItem($item);
            if (!empty($item['items'])) {
                $submenuOptions = ArrayHelper::merge($this->submenuOptions, ArrayHelper::getValue($item, 'submenuOptions', []));
                $submenuTag = ArrayHelper::remove($submenuOptions, 'tag', 'ul');
                $menu .= Html::tag($submenuTag, $this->renderItems($item['items']), $submenuOptions);
            }

            $lines[] = Html::tag($tag, $menu, $options);
        }
        return implode("\n", $lines);
    }

    /**
     * Renders the content of a menu item.
     * Note that the container and the sub-menus are not rendered here.
     * @param array $item the menu item to be rendered. Please refer to [[items]] to see what data might be in the item.
     * @return string the rendering result
     */
    protected function renderItem($item) {
        $linkOptions = ArrayHelper::merge($this->linkOptions, ArrayHelper::remove($item, 'options', []));
        $labelOptions = ArrayHelper::getValue($item, 'labelOptions', $this->labelOptions);
        $labelTag = ArrayHelper::remove($labelOptions, 'tag', 'span');
        $label = Html::tag($labelTag, $item['label'], $labelOptions);

        if (isset($item['url'])) {
            $template = ArrayHelper::getValue($item, 'linkTemplate', $this->linkTemplate);

            $icon = '';
            if (isset($item['icon'])) {
                $icon = '<span class="nav-icon">' . Html::tag('i', '', ['class' => $item['icon']]) . '</span>';
            }

            $badge = '';
            if (isset($item['badge'])) {
                $badgeCssClass = ArrayHelper::getValue($item, 'badgeCssClass', $this->badgeCssClass);
                $badge = Html::tag('span', $item['badge'], ['class' => $badgeCssClass]);
            }

            $text = strtr($template, [
                '{label}' => $label,
                '{icon}' => $icon,
                '{badge}' => $badge,
            ]);

            if (isset($item['items'])) {
                $arrowOptions = ['class' => 'arrow'];
                if ($item['active']) {
                    Html::addCssClass($arrowOptions, $this->toggleCssClass);
                }
                $text .= Html::tag('span', '', $arrowOptions);
                $item['url'] = 'javascript:;';
            }
            return Html::a($text, $item['url'], $linkOptions);
        } else {
            return $label;
        }
    }

    /**
     * Normalizes the [[items]] property to remove invisible items and activate certain items.
     * @param array $items the items to be normalized.
     * @param bool $active whether there is an active child menu item.
     * @return array the normalized menu items
     */
    protected function normalizeItems($items, &$active) {
        $view = $this->getView();
        foreach ($items as $i => $item) {
            if (!isset($item['label'])) {
                $item['label'] = '(no set)';
            }

            $route = ArrayHelper::getValue($item, 'route');
            if ($this->route == ltrim($route, '/')) {
                if ($view->context->module->defaultRoute !== $view->context->id) {
                    $view->params['breadcrumbs'][] = [
                        'label' => $item['label'],
                    ];
                }
            }
            $visible = ArrayHelper::getValue($item, 'visibled', true);
            if (!$visible) {
                unset($items[$i]);
                continue;
            }

            $encodeLabel = ArrayHelper::getValue($item, 'encode', $this->encodeLabels);
            if ($encodeLabel) {
                $items[$i]['label'] = Html::encode($item['label']);
            }

            $hasActiveChild = false;

            $_items = ArrayHelper::getValue($item, 'items');
            if (!empty($_items)) {
                $_items = $this->normalizeItems($_items, $hasActiveChild);
                if (count($_items) == 1) {
                    $items[$i] = $_items[0];
                } else {
                    $items[$i]['items'] = $_items;
                }
            } elseif ($_items !== null && $this->hideEmptyItems) {
                unset($items[$i]['items']);
                if (!isset($item['url'])) {
                    unset($items[$i]);
                    continue;
                }
            }

            if (!isset($item['active'])) {
                if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item)) {
                    $active = $items[$i]['active'] = true;
                } else {
                    $items[$i]['active'] = false;
                }
            } elseif ($item['active'] instanceof Closure) {
                $active = $items[$i]['active'] = call_user_func($item['active'], $item, $hasActiveChild, $this->isItemActive($item), $this);
            } elseif ($item['active']) {
                $active = true;
            }
        }

        return array_values($items);
    }

    /**
     * Checks whether a menu item is active.
     * @param array $item the menu item to be checked
     * @return bool whether the menu item is active
     */
    protected function isItemActive($item) {
        if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
            $route = Yii::getAlias($item['url'][0]);
            if ($route[0] !== '/' && Yii::$app->controller) {
                $route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
            }
            if (ltrim($route, '/') !== $this->route) {
                return false;
            }
            unset($item['url']['#']);
            if (count($item['url']) > 1) {
                $params = $item['url'];
                unset($params[0]);
                foreach ($params as $name => $value) {
                    if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
                        return false;
                    }
                }
            }

            return true;
        }

        return false;
    }

}
